﻿using System.Collections.Generic;

namespace System.Linq.Functional
{
    public static class MemoizeExtensions
    {
        public static Func<T, TResult> Memoize<T, TResult>(this Func<T, TResult> func)
        {
            var optional = func.MemoizeOptional();
            return t =>
            {
                return optional(t, false);
            };
        }
        
        public static Func<T1, T2, TResult> Memoize<T1, T2, TResult>(this Func<T1, T2, TResult> func)
        {
            var optional = func.MemoizeOptional();
            return (t1, t2) =>
            {
                return optional(t1, t2, false);
            };
        }

        public static Func<T, bool, TResult> MemoizeOptional<T, TResult>(this Func<T, TResult> func)
        {
            var dict = new Dictionary<T, TResult>();
            return (t, refresh) =>
            {
                if (!refresh && dict.ContainsKey(t))
                {
                    return dict[t];
                }
                else
                {
                    var result = func(t);
                    dict.Add(t, result);
                    return result;
                }
            };
        }

        public static Func<T1, T2, bool, TResult> MemoizeOptional<T1, T2, TResult>(this Func<T1, T2, TResult> func)
        {
            var dict = new Dictionary<Tuple<T1, T2>, TResult>();
            return (t1, t2, refresh) =>
            {
                var tuple = Tuple.New(t1, t2);
                if (!refresh && dict.ContainsKey(tuple))
                {
                    return dict[tuple];
                }
                else
                {
                    var result = func(tuple.Item1, tuple.Item2);
                    dict.Add(tuple, result);
                    return result;
                }
            };
        }

        public static Func<bool, TResult> MemoizeOptional<TResult>(this Func<TResult> func)
        {
            Maybe<TResult> result = Maybe<TResult>.Nothing;
            return (forceRefresh) =>
            {
                if (!forceRefresh && result.HasValue)
                {
                    return result.Value;
                }
                else
                {
                    var temp = func();
                    result = temp.ToMaybe();

                    return temp;
                }
            };
        }

        public static Action<bool, Action<T>, Action<Exception>> MemoizeOptionalAsync<T>(this Action<Action<T>, Action<Exception>> asyncFunc)
        {
            Maybe<T> result = Maybe<T>.Nothing;

            return (forceRefresh, onSuccess, onFailure) =>
                {
                    if (!forceRefresh && result.HasValue)
                    {
                        onSuccess(result.Value);
                    }
                    else
                    {
                        asyncFunc((t) =>
                        {
                            result = t.ToMaybe();
                            onSuccess(result.Value);
                        },
                        (exception) =>
                        {
                            result = Maybe<T>.Nothing;
                            onFailure(exception);
                        });                     
                    }
                };
        }
    }
}
